#define _GNU_SOURCE
#include <check.h>
#include "test_parser.h"

#include <stdio.h>
#include <signal.h>
#include <assert.h>

#include "parser.h"

/* Algunas variables/funciones auxiliares para ser usadas por el resto de los
 * tests
 */

static Lexer *lexer = NULL;
static FILE *input = NULL;
static pipeline *output = NULL;

static void init_lexer(char *content) {
	/* Inicializa `lexer' con un archivo simulado con `content' de
	 * contenido. Para no crear un archivo temporal, usa extensiones de GNU
	 */
	assert (lexer==NULL);
	assert (input==NULL);

	input = fmemopen(content, strlen(content), "r");
	lexer = lexer_new (input);
}

static void setup (void) {
}

static void teardown (void) {
	if (lexer != NULL) {
		lexer_destroy (lexer); lexer = NULL;
	}
	if (input != NULL) {
		fclose (input); input = NULL;
	}
	if (output != NULL) {
		pipeline_destroy (output); output = NULL;
	}
}

static void check_newline (void) {
	/* Comprueba que el lexer esté parado en un newline */
	fail_if (lexer_is_off (lexer));
	lexer_next_char (lexer, "\n");
	fail_unless (blength (lexer_item (lexer)) == 1);
	bdestroy (lexer_item (lexer));
}

static void check_argument (scommand *cmd, const char *first) {
	const_bstring arg = NULL;

	assert (cmd != NULL);
	assert (first != NULL);
	/* Comprueba que el primer elemento en `cmd' sea `first', y lo saca */
	fail_unless (! scommand_is_empty (cmd));
	
	arg = scommand_front (cmd);
	fail_unless (biseqcstr (arg, first) == 1);
	scommand_pop_front(cmd);
}

/* Testeo precondiciones. 
 * Se espera que todos estos tests fallen con un assert(), son todas 
 * llamadas inválidas 
 */
START_TEST (test_parse_null)
{
	output = parse_pipeline (NULL);
}
END_TEST

START_TEST (test_parse_closed)
{
	init_lexer("x");
	/* forzamos estado de is_off en el lexer */
	lexer_skip (lexer, ALPHA);
	lexer_skip (lexer, ALPHA);
	assert (lexer_is_off (lexer));

	/* Y tratamos de usar el parser (debería fallar) */
	output = parse_pipeline (lexer);
}
END_TEST

/* Algunas propiedades generales del parser. Sobre todo de cuanto 
 * input consume 
 */
START_TEST (test_consumes_until_newline)
{
	bstring after = NULL;

	init_lexer("ls\n--------\n\n\n");
	output = parse_pipeline (lexer);

	/* Debería haber quedado justo listo para consumir el '\n' */
	check_newline();

	/* Consumió hasta el \n equivocado? */
	fail_if (lexer_is_off (lexer)); 
	lexer_next (lexer, "-");
	after = lexer_item (lexer);
	/* Debería leer 8 veces "-", o sea que fue el primer \n: */
	fail_unless (blength(after) == 8); 
	bdestroy(after);
}
END_TEST

START_TEST (test_consumes_until_eof)
{
	init_lexer("ls");
	output = parse_pipeline (lexer);

	fail_unless (lexer_is_off (lexer)); /* No consumió todo! debería */
	fail_unless (feof (input));
}
END_TEST

/* Entradas válidas. Las básicas, como para que el baash se pueda usar, y
 * para sacar nota "A" 
 */

START_TEST (test_empty)
{
	scommand *s = NULL;

	init_lexer("\n");
	output = parse_pipeline (lexer);
	check_newline();
	/* Esto debería generar un pipeline de un elemento, con un comando
	 * vacío adentro
	 */
	fail_unless (pipeline_length (output) == 1);
	s = pipeline_front (output);
	fail_unless (scommand_length (s) == 0);
	/* Y no es en background */
	fail_unless (pipeline_get_wait (output));
	/*
	 * Según como hagan el parser, una alternativa es hacer que esto genere un
	 * parser de 0 elementos. Si lo prefieren lo pueden cambiar, pero tienen
	 * que cambiar este test para que se corresponda con eso.
	 */
}
END_TEST

START_TEST (test_command)
{
	scommand *s = NULL;

	init_lexer("comando\n");
	output = parse_pipeline (lexer);
	check_newline();
	/* Esto debería generar un pipeline de un elemento, con un comando
	 * de un elemento adentro
	 */
	fail_unless (pipeline_length (output) == 1);
	s = pipeline_front (output);
	fail_unless (scommand_length (s) == 1);
	check_argument (s, "comando");
	/* Sin redirección */
	fail_unless (scommand_get_redir_in (s) == NULL);
	fail_unless (scommand_get_redir_out (s) == NULL);
	/* Y no es en background */
	fail_unless (pipeline_get_wait (output));
}
END_TEST

START_TEST (test_command_with_args)
{
	scommand *s = NULL;

	init_lexer("comando arg1 arg2\n");
	output = parse_pipeline (lexer);
	check_newline();
	/* Esto debería generar un pipeline de un elemento, con un comando
	 * de 3 elementos adentro
	 */
	fail_unless (pipeline_length (output) == 1);
	s = pipeline_front (output);
	fail_unless (scommand_length (s) == 3);
	check_argument (s, "comando");
	check_argument (s, "arg1");
	check_argument (s, "arg2");
	/* Sin redirección */
	fail_unless (scommand_get_redir_in (s) == NULL);
	fail_unless (scommand_get_redir_out (s) == NULL);
	/* Y no es en background */
	fail_unless (pipeline_get_wait (output));
}
END_TEST

START_TEST (test_command_background)
{
	scommand *s = NULL;

	init_lexer("comando arg1 &\n");
	output = parse_pipeline (lexer);
	check_newline();
	/* Esto debería generar un pipeline con el flag de no esperar */
	fail_unless (pipeline_length (output) == 1);
	s = pipeline_front (output);
	fail_unless (scommand_length (s) == 2);
	check_argument (s, "comando");
	check_argument (s, "arg1");
	/* Sin redirección */
	fail_unless (scommand_get_redir_in (s) == NULL);
	fail_unless (scommand_get_redir_out (s) == NULL);
	/* Y no es en background */
	fail_unless (! pipeline_get_wait (output));
}
END_TEST

START_TEST (test_command_redir_in)
{
	scommand *s = NULL;

	init_lexer("comando arg1 < entrada\n");
	output = parse_pipeline (lexer);
	check_newline();
	/* Esto debería generar un pipeline de un elemento, con un comando
	 * redirigido adentro
	 */
	fail_unless (pipeline_length (output) == 1);
	s = pipeline_front (output);
	fail_unless (scommand_length (s) == 2);
	check_argument (s, "comando");
	check_argument (s, "arg1");
	/* con redirección */
	fail_unless (biseqcstr (scommand_get_redir_in (s), "entrada") == 1);
	fail_unless (scommand_get_redir_out (s) == NULL);
	/* Y no es en background */
	fail_unless (pipeline_get_wait (output));
}
END_TEST

START_TEST (test_command_redir_out)
{
	scommand *s = NULL;

	init_lexer("comando arg1 > salida\n");
	output = parse_pipeline (lexer);
	check_newline();
	/* Esto debería generar un pipeline de un elemento, con un comando
	 * redirigido adentro
	 */
	fail_unless (pipeline_length (output) == 1);
	s = pipeline_front (output);
	fail_unless (scommand_length (s) == 2);
	check_argument (s, "comando");
	check_argument (s, "arg1");
	/* con redirección */
	fail_unless (scommand_get_redir_in (s) == NULL);
	fail_unless (biseqcstr (scommand_get_redir_out (s), "salida") == 1);
	/* Y no es en background */
	fail_unless (pipeline_get_wait (output));
}
END_TEST

START_TEST (test_command_redir_both)
{
	scommand *s = NULL;

	init_lexer("comando arg1 < entrada > salida\n");
	output = parse_pipeline (lexer);
	check_newline();
	/* Esto debería generar un pipeline de un elemento, con un comando
	 * redirigido adentro
	 */
	fail_unless (pipeline_length (output) == 1);
	s = pipeline_front (output);
	fail_unless (scommand_length (s) == 2);
	check_argument (s, "comando");
	check_argument (s, "arg1");
	/* con redirección */
	fail_unless (biseqcstr (scommand_get_redir_in (s), "entrada") == 1);
	fail_unless (biseqcstr (scommand_get_redir_out (s), "salida") == 1);
	/* Y no es en background */
	fail_unless (pipeline_get_wait (output));
}
END_TEST

START_TEST (test_pipe_simple)
{
	scommand *s = NULL;

	init_lexer("comando | filtro\n");
	output = parse_pipeline (lexer);
	check_newline();
	/* Esto debería generar un pipeline de 2 elementos, cada uno con un comando
	 * simple adentro
	 */
	fail_unless (pipeline_length (output) == 2);
	s = pipeline_front (output);
	fail_unless (scommand_length (s) == 1);
	check_argument (s, "comando");
	/* Sin redirección */
	fail_unless (scommand_get_redir_in (s) == NULL);
	fail_unless (scommand_get_redir_out (s) == NULL);
	pipeline_pop_front (output);
	s = pipeline_front (output);
	fail_unless (scommand_length (s) == 1);
	check_argument (s, "filtro");
	/* Sin redirección */
	fail_unless (scommand_get_redir_in (s) == NULL);
	fail_unless (scommand_get_redir_out (s) == NULL);
	/* Y no es en background */
	fail_unless (pipeline_get_wait (output));
}
END_TEST

START_TEST (test_pipe_with_args)
{
	scommand *s = NULL;

	init_lexer("comando arg1 | filtro arg2\n");
	output = parse_pipeline (lexer);
	check_newline();
	/* Esto debería generar un pipeline de 2 elementos, cada uno con un comando
	 * y argumento adentro
	 */
	fail_unless (pipeline_length (output) == 2);
	s = pipeline_front (output);
	fail_unless (scommand_length (s) == 2);
	check_argument (s, "comando");
	check_argument (s, "arg1");
	/* Sin redirección */
	fail_unless (scommand_get_redir_in (s) == NULL);
	fail_unless (scommand_get_redir_out (s) == NULL);
	pipeline_pop_front (output);
	s = pipeline_front (output);
	fail_unless (scommand_length (s) == 2);
	check_argument (s, "filtro");
	check_argument (s, "arg2");
	/* Sin redirección */
	fail_unless (scommand_get_redir_in (s) == NULL);
	fail_unless (scommand_get_redir_out (s) == NULL);
	/* Y no es en background */
	fail_unless (pipeline_get_wait (output));
}
END_TEST

START_TEST (test_pipe_background)
{
	scommand *s = NULL;

	init_lexer("comando arg1 | filtro arg2 &\n");
	output = parse_pipeline (lexer);
	check_newline();
	/* Esto debería generar un pipeline de 2 elementos, cada uno con un comando
	 * y argumento adentro
	 */
	fail_unless (pipeline_length (output) == 2);
	s = pipeline_front (output);
	fail_unless (scommand_length (s) == 2);
	check_argument (s, "comando");
	check_argument (s, "arg1");
	/* Sin redirección */
	fail_unless (scommand_get_redir_in (s) == NULL);
	fail_unless (scommand_get_redir_out (s) == NULL);
	pipeline_pop_front (output);
	s = pipeline_front (output);
	fail_unless (scommand_length (s) == 2);
	check_argument (s, "filtro");
	check_argument (s, "arg2");
	/* Sin redirección */
	fail_unless (scommand_get_redir_in (s) == NULL);
	fail_unless (scommand_get_redir_out (s) == NULL);
	/* Y no es en background */
	fail_unless (! pipeline_get_wait (output));
}
END_TEST

START_TEST (test_non_alphabetic_args)
{
	scommand *s = NULL;
	/* Nos interesa que el parser se banque argumentos que tengan cualquier
	 * caracter que no signifique algo especial. Guiones, barras, igual,
	 * números son ejemplos de cosas que suelen ir en argumentos.
	 */
	init_lexer("comando --size=11 /etc/passwd\n");
	output = parse_pipeline (lexer);
	check_newline();
	fail_unless (pipeline_length (output) == 1);
	s = pipeline_front (output);
	fail_unless (scommand_length (s) == 3);
	check_argument (s, "comando");
	check_argument (s, "--size=11");
	check_argument (s, "/etc/passwd");
}
END_TEST

START_TEST (test_many_args)
{
	scommand *s = NULL;
	/* Nos interesa que el parser se banque cualquier cantidad de argumentos */
#define ARGLIST_10 " 1 2 3 4 5 6 7 8 9 10"
#define ARGLIST_100 ARGLIST_10 ARGLIST_10 ARGLIST_10 ARGLIST_10 ARGLIST_10 \
                    ARGLIST_10 ARGLIST_10 ARGLIST_10 ARGLIST_10 ARGLIST_10

	init_lexer("comando" ARGLIST_100 ARGLIST_100 "\n");
	output = parse_pipeline (lexer);
	check_newline();
	fail_unless (pipeline_length (output) == 1);
	s = pipeline_front (output);
	/* 1 comando + 200 argumentos = 201 elementos */
	fail_unless (scommand_length (s) == 201);
	check_argument (s, "comando");
	/* Supongamos que los argumentos están bien, basado en tests anteriores. */
}
END_TEST

/* Armado de la test suite */

Suite *parser_suite (void)
{
	Suite *s = suite_create ("parser");
	TCase *tc_preconditions = tcase_create ("Precondition");
	TCase *tc_general = tcase_create ("General");
	TCase *tc_valid = tcase_create ("Valid");
	TCase *tc_invalid = tcase_create ("Invalid");
	TCase *tc_valid2 = tcase_create ("Valid(hard)");
	TCase *tc_invalid2 = tcase_create ("Invalid(hard)");

	/* Precondiciones */
	tcase_add_checked_fixture (tc_preconditions, setup, teardown);
	tcase_add_test_raise_signal (tc_preconditions, test_parse_null, SIGABRT);
	tcase_add_test_raise_signal (tc_preconditions, test_parse_closed, SIGABRT);
	suite_add_tcase (s, tc_preconditions);

	/* Propiedades generales del parser */
	tcase_add_checked_fixture (tc_general, setup, teardown);
	tcase_add_test (tc_general, test_consumes_until_newline);
	tcase_add_test (tc_general, test_consumes_until_eof);
	suite_add_tcase (s, tc_general);

	/* Entradas válidas, simples */
	tcase_add_checked_fixture (tc_valid, setup, teardown);
	tcase_add_test (tc_valid, test_empty);
	tcase_add_test (tc_valid, test_command);
	tcase_add_test (tc_valid, test_command_with_args);
	tcase_add_test (tc_valid, test_command_background);
	tcase_add_test (tc_valid, test_command_redir_in);
	tcase_add_test (tc_valid, test_command_redir_out);
	tcase_add_test (tc_valid, test_command_redir_both);
	tcase_add_test (tc_valid, test_pipe_simple);
	tcase_add_test (tc_valid, test_pipe_with_args);
	tcase_add_test (tc_valid, test_pipe_background);
	tcase_add_test (tc_valid, test_non_alphabetic_args);
	tcase_add_test (tc_valid, test_many_args);
	suite_add_tcase (s, tc_valid);

	/* Chequeos de error básicos */
	tcase_add_checked_fixture (tc_invalid, setup, teardown);
	suite_add_tcase (s, tc_invalid);

	/* Entradas válidas, complejas */
	tcase_add_checked_fixture (tc_valid2, setup, teardown);
	suite_add_tcase (s, tc_valid2);

	/* Entradas inválidas, complejas */
	tcase_add_checked_fixture (tc_invalid2, setup, teardown);
	suite_add_tcase (s, tc_invalid2);

	return s;
}

/* Para testing de memoria */
void parser_memory_test (void) {
	/* Las siguientes operaciones deberían poder hacer sin leaks ni doble 
	 * frees.
	 */
	init_lexer ("ls");
	output = parse_pipeline (lexer);
	teardown();
}

